package com.cloud.tool.aop;

import com.cloud.tool.annotation.JasyptField;
import com.cloud.tool.annotation.JasyptMethod;
import com.cloud.tool.common.JasyptConstant;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.jasypt.encryption.StringEncryptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

/**
 * @Classname EncryptHandler
 * @Date 2021/8/3 14:34
 * @Author WangZY
 * @Description 数据加密切面
 */
@Aspect
@Component
@Slf4j
public class JasyptHandler {

    private final StringEncryptor stringEncryptor;

    public JasyptHandler(StringEncryptor stringEncryptor) {
        this.stringEncryptor = stringEncryptor;
    }

    @Pointcut("@annotation(com.cloud.tool.annotation.JasyptMethod)")
    public void pointCut() {
    }

    @Around("pointCut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        JasyptMethod jasyptMethod = methodSignature.getMethod().getAnnotation(JasyptMethod.class);
        String type = jasyptMethod.type();
        Object[] objects = joinPoint.getArgs();
        if (JasyptConstant.ENCRYPT.equalsIgnoreCase(type)) {
            try {
                if (objects.length != 0) {
                    for (int i = 0; i < objects.length; i++) {
                        if (objects[i] instanceof String) {
                            objects[i] = stringEncryptor.encrypt(String.valueOf(objects[i]));
                        } else {
                            handlerObj(objects[i], JasyptConstant.ENCRYPT, objects[i] instanceof List<?>);
                        }
                    }
                }
            } catch (Exception e) {
                log.error("TOOLBOX 数据加密失败", e);
            }
        }
        Object obj = joinPoint.proceed(objects);
        if (JasyptConstant.DECRYPT.equalsIgnoreCase(type) && obj != null) {
            if (obj instanceof String) {
                obj = stringEncryptor.decrypt(String.valueOf(obj));
            } else {
                handlerObj(obj, JasyptConstant.DECRYPT, obj instanceof List);
            }
        }
        return obj;
    }

    private void handlerObj(Object obj, String type, boolean isList) {
        if (isList) {
            List<Object> castList = castList(obj, Object.class);
            for (Object o : castList) {
                Field[] fieldList = o.getClass().getDeclaredFields();
                objRun(o, type, fieldList);
            }
        } else {
            Field[] fields = obj.getClass().getDeclaredFields();
            objRun(obj, type, fields);
        }
    }

    private void objRun(Object obj, String type, Field[] fields) {
        for (Field field : fields) {
            boolean hasSecureField = field.isAnnotationPresent(JasyptField.class);
            if (hasSecureField) {
                try {
                    //关闭JDK安全检查，提高反射速度
                    field.setAccessible(true);
                    String realValue = (String) field.get(obj);
                    String value;
                    if (JasyptConstant.DECRYPT.equals(type)) {
                        value = stringEncryptor.decrypt(realValue);
                    } else {
                        value = stringEncryptor.encrypt(realValue);
                    }
                    field.set(obj, value);
                } catch (Exception e) {
                    log.error("TOOLBOX 反射修改对象属性失败", e);
                }
            }
        }
    }

    public static <T> List<T> castList(Object obj, Class<T> clazz) {
        List<T> result = new ArrayList<T>();
        if (obj instanceof List<?>) {
            for (Object o : (List<?>) obj) {
                result.add(clazz.cast(o));
            }
            return result;
        }
        return null;
    }
}
